SPB Git forge

spb/immbot-ai

Public
1commits 1branches 0releases
1.5 MBsize
maindefault branch
20 days agolast push
TypeScript 98.3% CSS 0.9% Shell 0.7%
4.0 KB · 99 lines typescript
Raw Blame History
1// Tableau de progression : maîtrise par concept/semaine, activité, série, statistiques.2import { NextResponse } from "next/server";3import { apiError, requireEnrollment } from "@/lib/api.ts";4import { requireUser } from "@/lib/auth/session.ts";5import { all, get } from "@/lib/db/index.ts";6import { masteryForCourse, LEVEL_LABELS } from "@/lib/learning/mastery.ts";7import { recommendations } from "@/lib/learning/recommend.ts";8import { normalizeCourse } from "@/lib/learning/helpers.ts";910export async function GET(_req: Request, ctx: { params: Promise<{ course: string }> }) {11  try {12    const user = await requireUser();13    const course = normalizeCourse((await ctx.params).course);14    requireEnrollment(user.id, course);1516    const mastery = masteryForCourse(user.id, course);17    const practiced = mastery.filter((m) => m.observations > 0);18    const globalScore = mastery.length19      ? mastery.reduce((s, m) => s + m.score * m.importance, 0) / mastery.reduce((s, m) => s + m.importance, 0)20      : 0;2122    // Par semaine23    const weeks = new Map<number, { score: number; n: number }>();24    for (const m of mastery) {25      const w = m.week ?? 0;26      const e = weeks.get(w) ?? { score: 0, n: 0 };27      e.score += m.score;28      e.n++;29      weeks.set(w, e);30    }3132    // Série de jours actifs33    const days = all<{ d: string }>(34      "SELECT DISTINCT date(created_at) as d FROM activity_log WHERE user_id = ? ORDER BY d DESC LIMIT 60",35      user.id36    ).map((r) => r.d);37    let streak = 0;38    const today = new Date();39    for (let i = 0; i < 60; i++) {40      const d = new Date(today);41      d.setDate(d.getDate() - i);42      const iso = d.toISOString().slice(0, 10);43      if (days.includes(iso)) streak++;44      else if (i > 0) break; // aujourd'hui pas encore actif ne casse pas la série d'hier45    }4647    const stats = {48      cardsReviewed: get<{ n: number }>(49        `SELECT COUNT(*) as n FROM review_log rl JOIN flashcards f ON f.id = rl.card_id WHERE rl.user_id = ? AND f.course_code = ?`,50        user.id, course51      )?.n ?? 0,52      quizAnswered: get<{ n: number }>(53        `SELECT COUNT(*) as n FROM quiz_answers qa JOIN quiz_sessions qs ON qs.id = qa.session_id WHERE qs.user_id = ? AND qs.course_code = ?`,54        user.id, course55      )?.n ?? 0,56      quizAccuracy: get<{ p: number }>(57        `SELECT ROUND(AVG(qa.correct)*100) as p FROM quiz_answers qa JOIN quiz_sessions qs ON qs.id = qa.session_id WHERE qs.user_id = ? AND qs.course_code = ?`,58        user.id, course59      )?.p ?? null,60      examAttempts: get<{ n: number }>(61        `SELECT COUNT(*) as n FROM exam_attempts ea JOIN mock_exams me ON me.id = ea.exam_id WHERE ea.user_id = ? AND me.course_code = ? AND ea.finished_at IS NOT NULL`,62        user.id, course63      )?.n ?? 0,64      studyMinutes: Math.round((get<{ s: number }>(65        "SELECT COALESCE(SUM(duration_s),0) as s FROM activity_log WHERE user_id = ? AND course_code = ?",66        user.id, course67      )?.s ?? 0) / 60),68      errorsToReview: get<{ n: number }>(69        "SELECT COUNT(*) as n FROM error_notebook WHERE user_id = ? AND course_code = ? AND status = 'a-revoir'",70        user.id, course71      )?.n ?? 0,72      cardsDue: get<{ n: number }>(73        `SELECT COUNT(*) as n FROM card_states cs JOIN flashcards f ON f.id = cs.card_id74         WHERE cs.user_id = ? AND f.course_code = ? AND cs.suspended = 0 AND cs.due_at <= datetime('now')`,75        user.id, course76      )?.n ?? 0,77    };7879    const recent = all(80      `SELECT kind, meta, created_at FROM activity_log WHERE user_id = ? AND course_code = ? ORDER BY id DESC LIMIT 10`,81      user.id, course82    );8384    return NextResponse.json({85      globalScore,86      levels: LEVEL_LABELS,87      concepts: mastery,88      practicedCount: practiced.length,89      weekScores: [...weeks.entries()].map(([week, v]) => ({ week, score: v.score / v.n })).sort((a, b) => a.week - b.week),90      streak,91      stats,92      recent,93      recommendations: recommendations(user.id, course),94    });95  } catch (e) {96    return apiError(e);97  }98}99